package org.xx.armory.spring5.mvc;

import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.xx.armory.commons.CharArray;
import org.xx.armory.spring5.mvc.converters.StringToBooleanConverter;
import org.xx.armory.spring5.mvc.converters.StringToDateConverter;
import org.xx.armory.spring5.mvc.converters.StringToIntegerEnumConverterFactory;
import org.xx.armory.spring5.mvc.converters.StringToNumberConverter;
import org.xx.armory.spring5.mvc.converters.StringToStringEnumConverterFactory;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.nio.ByteBuffer;
import java.text.DecimalFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import static java.util.Arrays.stream;
import static org.xx.armory.commons.StringConverter.parseByteBuffer;
import static org.xx.armory.commons.StringConverter.parseYearMonth;

@Order(10)
@EnableWebMvc
public class WebMvcConfig
        implements WebMvcConfigurer, WebApplicationInitializer {
    private final Logger logger = LoggerFactory.getLogger(WebMvcConfig.class);

    @Autowired(required = false)
    private RewriteSimpleURLInterceptor rewriteSimpleURLInterceptor;

    @Autowired(required = false)
    private LogInterceptor logInterceptor;

    @Autowired(required = false)
    private VerifyRequestParamsSignatureInterceptor verifyRequestParamsSignatureInterceptor;

    @Autowired(required = false)
    private AddAuthenticationToViewInterceptor addAuthenticationToViewInterceptor;

    @Autowired(required = false)
    private OutputCacheInterceptor outputCacheInterceptor;

    @Override
    public void onStartup(
            ServletContext servletContext
    )
            throws ServletException {
        logger.info("Servlet started up");
    }

    @Override
    public void configureDefaultServletHandling(
            DefaultServletHandlerConfigurer configurer
    ) {
        logger.info("Enable static resource handler");

        configurer.enable();
    }

    @Override
    public void addFormatters(
            FormatterRegistry registry
    ) {
        logger.info("Add custom argument formatter");

        registry.addConverter(String.class, String.class, String::strip);
        registry.addConverter(String.class, CharArray.class, s -> new CharArray(s.toCharArray()));
        registry.addConverter(String.class, PreservedString.class, PreservedString::new);
        registry.addConverter(new StringToNumberConverter());
        registry.addConverter(new StringToBooleanConverter());
        registry.addConverter(new StringToDateConverter());
        registry.addConverter(String.class, ByteBuffer.class, source -> parseByteBuffer(source, null));
        registry.addConverter(String.class, YearMonth.class, source -> parseYearMonth(source, null));
        registry.addConverterFactory(new StringToIntegerEnumConverterFactory());
        registry.addConverterFactory(new StringToStringEnumConverterFactory());

        final var currencyFormatter = new DecimalFormat("#,##0.00");
        registry.addConverter(Money.class, String.class, m -> currencyFormatter.format(m.value()));

        final var localDateFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd");
        registry.addConverter(LocalDate.class, String.class, localDateFormatter::format);

        final var localDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss");
        registry.addConverter(LocalDateTime.class, String.class, localDateTimeFormatter::format);

        final var instantFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss ZZ");
        registry.addConverter(Instant.class, String.class, instantFormatter::format);

        final var dateFormat = FastDateFormat.getInstance("yyyy.MM.dd HH:mm:ss");
        registry.addConverter(Date.class, String.class, dateFormat::format);
    }

    @Override
    public void addInterceptors(
            InterceptorRegistry registry
    ) {
        logger.info("Enable output cache");

        addHandlerInterceptors(
                registry,
                this.rewriteSimpleURLInterceptor,
                this.outputCacheInterceptor,
                this.verifyRequestParamsSignatureInterceptor,
                this.logInterceptor);

        addWebRequestInterceptors(
                registry,
                this.addAuthenticationToViewInterceptor
        );
    }

    @Override
    public void addResourceHandlers(
            ResourceHandlerRegistry registry
    ) {
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/");
    }

    @Override
    public void addViewControllers(
            ViewControllerRegistry registry
    ) {
        registry.addRedirectViewController("/", "/portal/index");
        registry.addRedirectViewController("/index", "/portal/index");
    }

    @Override
    public void addArgumentResolvers(
            List<HandlerMethodArgumentResolver> resolvers
    ) {
        logger.info("Add remote address argument resolver");

        resolvers.add(new RemoteAddressArgumentResolver());
    }

    private void addHandlerInterceptors(
            InterceptorRegistry registry,
            HandlerInterceptor... interceptors
    ) {
        stream(interceptors)
                .filter(Objects::nonNull)
                .forEach(registry::addInterceptor);
    }

    private void addWebRequestInterceptors(
            InterceptorRegistry registry,
            WebRequestInterceptor... interceptors
    ) {
        stream(interceptors)
                .filter(Objects::nonNull)
                .forEach(registry::addWebRequestInterceptor);
    }
}
